/*
 * cycle_counter.h
 *
 *  Created on: Aug 4, 2013
 *      Author: Ken Arok
 *
 *      Revision history:
 *      ---------------------------------------------------------
 *      Date			|	Revised by		|	Description
 *      ---------------------------------------------------------
 *      1. Aug 4, 2013	|	Yosef			| New establishment
 *
 *
 *
 * \brief Hardware Abstraction Layer of Cycle counter UC3C0512C
 *
 * Copyright (c) 2013 PT Hanindo Automation Solutions. All rights reserved.
 *
 */

#include "config_board.h"

#if BOARD_1_0_USED

#ifndef CYCLE_COUNTER_H_
#define CYCLE_COUNTER_H_

#include "compiler.h"

#ifdef __cplusplus
extern "C" {
#endif


#define CPU_TIMER_STATE_STARTED 0
#define CPU_TIMER_STATE_REACHED 1
#define CPU_TIMER_STATE_STOPPED 2

//! Structure holding private information, automatically initialized by the
//! cpu_set_timeout() function.
typedef struct
{
	//! The cycle count at the beginning of the timeout.
	unsigned long delay_start_cycle;

	//! The cycle count at the end of the timeout.
	unsigned long delay_end_cycle;

	//! Enable/disable the timeout detection
	unsigned char timer_state;
} t_cpu_time;


/*!
 * \brief Convert milli-seconds into CPU cycles.
 *
 * \param  ms:      Number of millisecond.
 * \param  fcpu_hz: CPU frequency in Hz.
 *
 * \return the converted number of CPU cycles.
 */
__always_inline static uint32_t cpu_ms_2_cy(unsigned long ms, unsigned long fcpu_hz)
{
	return ((unsigned long long)ms * fcpu_hz + 999) / 1000;
}


/*!
 * \brief Convert micro-seconds into CPU cycles.
 *
 * \param  us:      Number of microsecond.
 * \param  fcpu_hz: CPU frequency in Hz.
 *
 * \return the converted number of CPU cycles.
 */
__always_inline static uint32_t cpu_us_2_cy(unsigned long us, unsigned long fcpu_hz)
{
	return ((unsigned long long)us * fcpu_hz + 999999) / 1000000;
}


/*!
 * \brief Convert CPU cycles into milli-seconds.
 *
 * \param  cy:      Number of CPU cycles.
 * \param  fcpu_hz: CPU frequency in Hz.
 *
 * \return the converted number of milli-second.
 */
__always_inline static uint32_t cpu_cy_2_ms(unsigned long cy, unsigned long fcpu_hz)
{
	return ((unsigned long long)cy * 1000 + fcpu_hz-1) / fcpu_hz;
}


/*!
 * \brief Convert CPU cycles into micro-seconds.
 *
 * \param  cy:      Number of CPU cycles.
 * \param  fcpu_hz: CPU frequency in Hz.
 *
 * \return the converted number of micro-second.
 */
__always_inline static uint32_t cpu_cy_2_us(unsigned long cy, unsigned long fcpu_hz)
{
	return ((unsigned long long)cy * 1000000 + fcpu_hz-1) / fcpu_hz;
}


/*!
 * \brief Set a timer variable.
 *
 * Ex:  t_cpu_time timer;
 *      cpu_set_timeout(cpu_ms_2_cy(10, FOSC0), &timer); // timeout in 10 ms
 *      if(cpu_is_timeout(&timer))
 *         cpu_stop_timeout(&timer);
 *         ../..
 *
 * \param  delay:   (input) delay in CPU cycles before timeout.
 * \param  cpu_time: (output) internal information used by the timer API.
 */
__always_inline static void cpu_set_timeout(unsigned long delay, t_cpu_time *cpu_time)
{
	cpu_time->delay_start_cycle = Get_system_register(AVR32_COUNT);
	cpu_time->delay_end_cycle   = cpu_time->delay_start_cycle + delay;
	cpu_time->timer_state       = CPU_TIMER_STATE_STARTED;

	/* This is added to wake up interrupt. */
	if(!Is_global_exception_enabled()) {
		Enable_global_exception();
	}
	if(!cpu_irq_is_enabled()) {
		cpu_irq_enable();
	}
}


/*!
 * \brief Test if a timer variable reached its timeout.
 *
 * Once the timeout is reached, the function will always return true,
 * until the cpu_stop_timeout() function is called.
 *
 * Ex:  t_cpu_time timer;
 *      cpu_set_timeout(10, FOSC0, &timer); // timeout in 10 ms
 *      if(cpu_is_timeout(&timer))
 *         cpu_stop_timeout(&timer);
 *         ../..
 *
 * \param  cpu_time:   (input) internal information used by the timer API.
 *
 * \return true if timeout occurred, otherwise false.
 */
__always_inline static unsigned long cpu_is_timeout(t_cpu_time *cpu_time)
{
	unsigned long current_cycle_count = Get_system_register(AVR32_COUNT);

	if(cpu_time->timer_state==CPU_TIMER_STATE_STOPPED)
		return false;

	// Test if the timeout as already occurred.
	else if (cpu_time->timer_state == CPU_TIMER_STATE_REACHED)
		return true;

	// If the ending cycle count of this timeout is wrapped, ...
	else if (cpu_time->delay_start_cycle > cpu_time->delay_end_cycle)
	{
		if (current_cycle_count < cpu_time->delay_start_cycle && current_cycle_count > cpu_time->delay_end_cycle)
		{
			cpu_time->timer_state = CPU_TIMER_STATE_REACHED;
			return true;
		}
		return false;
	}
	else
	{
		if (current_cycle_count < cpu_time->delay_start_cycle || current_cycle_count > cpu_time->delay_end_cycle)
		{
			cpu_time->timer_state = CPU_TIMER_STATE_REACHED;
			return true;
		}
		return false;
	}
}


/*!
 * \brief Stop a timeout detection.
 *
 * Ex:  t_cpu_time timer;
 *      cpu_set_timeout(10, FOSC0, &timer); // timeout in 10 ms
 *      if(cpu_is_timeout(&timer))
 *         cpu_stop_timeout(&timer);
 *         ../..
 *
 * \param  cpu_time:   (input) internal information used by the timer API.
 */
__always_inline static void cpu_stop_timeout(t_cpu_time *cpu_time)
{
	cpu_time->timer_state = CPU_TIMER_STATE_STOPPED;
}


/*!
 * \brief Test if a timer is stopped.
 *
 * \param  cpu_time:   (input) internal information used by the timer API.
 *
 * \return true if timer is stopped, otherwise false.
 */
__always_inline static unsigned long cpu_is_timer_stopped(t_cpu_time *cpu_time)
{
	if(cpu_time->timer_state==CPU_TIMER_STATE_STOPPED)
		return true;
	else
		return false;
}


/*!
 * \brief Waits during at least the specified delay (in millisecond) before returning.
 *
 * \param  delay:   Number of millisecond to wait.
 * \param  fcpu_hz: CPU frequency in Hz.
 */
__always_inline static void cpu_delay_ms(unsigned long delay, unsigned long fcpu_hz)
{
	t_cpu_time timer;
	cpu_set_timeout(cpu_ms_2_cy(delay, fcpu_hz), &timer);
	while(!cpu_is_timeout(&timer));
}

/*!
 * \brief Waits during at least the specified delay (in microsecond) before returning.
 *
 * \param  delay:   Number of microsecond to wait.
 * \param  fcpu_hz: CPU frequency in Hz.
 */
__always_inline static void cpu_delay_us(unsigned long delay, unsigned long fcpu_hz)
{
	t_cpu_time timer;
	cpu_set_timeout(cpu_us_2_cy(delay, fcpu_hz), &timer);
	while(!cpu_is_timeout(&timer));
}

/*!
 * \brief Waits during at least the specified delay (in CPU cycles) before returning.
 *
 * \param  delay:   Number of CPU cycles to wait.
 */
__always_inline static void cpu_delay_cy(unsigned long delay)
{
	t_cpu_time timer;
	cpu_set_timeout(delay, &timer);
	while(!cpu_is_timeout(&timer));
}


#define Get_sys_count()     (Get_system_register(AVR32_COUNT)       )
#define Set_sys_count(x)    (Set_system_register(AVR32_COUNT,   (x)))
#define Get_sys_compare()   (Get_system_register(AVR32_COMPARE)     )
#define Set_sys_compare(x)  (Set_system_register(AVR32_COMPARE, (x)))


#ifdef __cplusplus
}
#endif

#endif /* CYCLE_COUNTER_H_ */

#endif /* BOARD_1_0_USED */
